package cc.eddic.pratice.programmingparadigms.advanced;

import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import org.reflections.scanners.Scanners;

import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Predicate;

@Slf4j
public class BeanFactory {
    public static <T> T getBean(Class<T> clazz) {
        String abbr = System.getenv("SONY_JAVA_PRACTICE_USER_ABBR");
        if (log.isDebugEnabled()) log.debug("SONY_JAVA_PRACTICE_USER_ABBR: {}", abbr);
        return getBean(clazz, abbr);
    }

    public static <T> T getBean(Class<T> clazz, String abbr) {
        Predicate<Class<?>> suffixPredicate = c -> c.getSimpleName().endsWith(abbr + "Impl");

        // TODO: 从 instanceMap 取「单例」对象，

        Reflections reflections = new Reflections("cc.eddic", Scanners.SubTypes);
        Class<?> tp = reflections.getSubTypesOf(clazz).stream()
                .filter(suffixPredicate)
                .findFirst().orElseThrow(() -> new GetBeanException("Not found " + abbr + " implementation of " + clazz.getTypeName()));
        try {
            T t = (T) tp.newInstance();
            if (log.isInfoEnabled()) log.info("Bean: {}", t.getClass().getTypeName());
            return t;
        } catch (InstantiationException | IllegalAccessException e) {
            throw new GetBeanException(e.getMessage(), e);
        }
    }

    public static void scanEddicInterface() {
        if (log.isInfoEnabled()) log.info("Scan @EddicInterface");

        Reflections reflections = new Reflections("cc.eddic");
        for (Class<?> clazz : reflections.getTypesAnnotatedWith(EddicInterface.class)) {
            if (clazz.isInterface()) {
                if (log.isDebugEnabled()) log.debug("EddicInterface: {}", clazz.getTypeName());
                classMap.put(clazz, new HashSet<>());
                continue;
            }
            // https://www.baeldung.com/java-reflection-is-class-abstract
            if (Modifier.isAbstract(clazz.getModifiers())) {
                if (log.isInfoEnabled()) log.info("Skip abstract class: {}", clazz.getTypeName());
                continue;
            }

            if (log.isDebugEnabled()) log.debug("  {}", clazz.getTypeName());
            classMap.keySet().stream().filter(c -> Arrays.stream(clazz.getInterfaces()).anyMatch(x -> x.getTypeName().equals(c.getTypeName()))).forEach(c -> {
                classMap.get(c).add(clazz);
            });
        }
        if (log.isInfoEnabled()) {
            classMap.forEach((key, value) -> {
                log.info("{}:", key.getTypeName());
                value.forEach(x -> log.info("  {}", x.getTypeName()));
            });
        }

        // TODO: 应只建立「单例」的对象
        if (log.isInfoEnabled()) log.info("Create instance");
        classMap.values().stream().flatMap(Collection::stream).forEach(x -> {
            try {
                if (log.isDebugEnabled()) log.debug("Create instance: {}", x.getTypeName());
                instanceMap.put(x, x.newInstance());
            } catch (InstantiationException | IllegalAccessException e) {
                throw new GetBeanException(e.getMessage(), e);
            }
        });
    }

    static Map<Class<?>, Set<Class<?>>> classMap = new HashMap<>();

    static Map<Class<?>, Object> instanceMap = new HashMap<>();

    public static class GetBeanException extends RuntimeException {
        public GetBeanException(String message) {
            super(message);
        }

        public GetBeanException(String message, Throwable cause) {
            super(message, cause);
        }
    }
}
